﻿/*
 * Project: IV:MP GUI Editor (http://code.google.com/p/ivmp-gui-editor)
 * Code license: GNU Lesser GPL (http://www.gnu.org/copyleft/lesser.html)
 * 
 * Authors: CrackHD
 * Description: Communication between application instances
 */

using System;
using System.IO;
using System.IO.Pipes;
using System.Security.Principal;
using System.Text;
using System.Threading;

namespace IvmpEditor
{
    public static class NamedPipes
    {
        // Server named pipes stream is used to receive commands from "client" streams
        // (from another instances) and execute them in IDE.
        static NamedPipeServerStream serverPipe;

        // Client named pipes stream is used to send a commands (open file, etc)
        // to another stream, e.g. its named pipes server.
        static NamedPipeClientStream clientPipe;

        // This lock is used to ensure server is already started, this will make impossible attempt of starting second server
        // Method checks if any server on this PC is already running
        static LockFile lockPipeServer = new LockFile("lockPipeServer.lock");
        public static bool IsAnyServerAlreadyRunning()
        {
            if (IsServerStarted)
                throw new InvalidOperationException("Invalid operation; we have own server aready running.");

            // this will also hold a pipe server lock *
            lockPipeServer.TryHold();
            return !lockPipeServer.IsHolden;
        }

        // Code in this file shows a simplest example of named pipes usage,
        // it has a server which only receives data from everyone and client which can only send a command to server pipe.
        const string PIPE_NAME = "IvmpGuiEditorIDE";

        // Server pipe stream:
        public static bool IsServerStarted { get { return serverPipe != null; } }
        public static void RunServer()
        {
            if (IsServerStarted)
                return;
            else if (!lockPipeServer.IsHolden)
                if (!lockPipeServer.TryHold())
                    throw new ApplicationException("When was starting named pipes server, was attempt to hold lock file, which failed...");

            serverPipe = new NamedPipeServerStream(PIPE_NAME, PipeDirection.In, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous, 3333, 0);
            serverPipe.BeginWaitForConnection(Server_ClientConnected, serverPipe);
        }
        static void Server_ClientConnected(IAsyncResult ar)
        {
            if (serverPipe == null)
                return;

            try
            {
                serverPipe.EndWaitForConnection(ar);
                serverPipe.RunAsClient(
                    delegate
                    {
                        // Read a string from pipe stream, from client:
                        var stringReader = new ByteString(serverPipe, Encoding.Default);
                        var strCommand = stringReader.ReadString();

                        var mainWnd = Editor.GetInstance().MainWindow;
                        mainWnd.Dispatcher.Invoke((Action<eIDECommand, string>)mainWnd.ExecuteIDECommand, Common.IdeCommandFromString(strCommand, out strCommand), strCommand);
                    });

                if (serverPipe.IsConnected)
                    serverPipe.Disconnect();
            }
            catch { }

            if (serverPipe != null)
                serverPipe.BeginWaitForConnection(Server_ClientConnected, serverPipe);
        }
        public static void StopServer()
        {
            if (!IsServerStarted)
                return;

            if(serverPipe.IsConnected)
                serverPipe.Disconnect();

            serverPipe.Close();
            serverPipe = null;

            Thread.Sleep(100);
            lockPipeServer.Release();
        }

        // Client pipeStream:
        public static void SendCommandToServer(eIDECommand cmd, string[] cmdArgs)
        {
            var sArgs = string.Empty;
            for (var i = 0; i < cmdArgs.Length; i++)
                sArgs += cmdArgs[i] + (i != cmdArgs.Length - 1 ? " " : null);
            SendCommandToServer(cmd, sArgs);
        }
        public static void SendCommandToServer(eIDECommand cmd, string cmdArgs = null)
        {
            clientPipe = new NamedPipeClientStream(".", PIPE_NAME, PipeDirection.Out, PipeOptions.None, TokenImpersonationLevel.Impersonation);
            clientPipe.Connect(3 * 1000);

            var stringWriter = new ByteString(clientPipe, Encoding.Default);
            var strCommand = Common.IdeCommandToString(cmd, cmdArgs);
            stringWriter.WriteString(strCommand);
            clientPipe.WaitForPipeDrain();
            
            //clientPipe.Close();
            clientPipe = null;
        }
    }
    public class ByteString
    {
        // "StreamString" From MSDN example.
        // Simple writes or reads strings in stream

        Stream ioStream;
        Encoding streamEncoding;

        public ByteString(Stream ioStream, Encoding useEncoding)
        {
            this.ioStream = ioStream;
            streamEncoding = useEncoding;
        }

        public string ReadString()
        {
            int len;
            len = ioStream.ReadByte() * 256;
            len += ioStream.ReadByte();
            byte[] inBuffer = new byte[len];
            ioStream.Read(inBuffer, 0, len);

            return streamEncoding.GetString(inBuffer);
        }
        public int WriteString(string outString)
        {
            byte[] outBuffer = streamEncoding.GetBytes(outString);
            int len = outBuffer.Length;
            if (len > UInt16.MaxValue)
            {
                len = (int)UInt16.MaxValue;
            }
            ioStream.WriteByte((byte)(len / 256));//comment: thanks to the author of this code, i love my ass and wanna look pro coder too
            ioStream.WriteByte((byte)(len & 255));//...instead of Write(BitConverter.GetBytes(len)).
            ioStream.Write(outBuffer, 0, len);
            ioStream.Flush();

            return outBuffer.Length + 2;
        }
    }
}